package tool.animation;

import java.awt.Component;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.Thread.State;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.JComponent;

/** Rappresenta quel concetto di animazione fondato sull'iterazione
 *  di un dato set di immagini discretizzate temporalmente.
 * @author Daniele
 * @version 1.2 */
public class DiscreteAnimation implements AbstractAnimation, Runnable,Serializable,Cloneable {

	private static final long serialVersionUID = 5724445894053240015L;
	
	/** Il set di immagini su cui ciclare.*/
	private Vector<ImageIcon> frames;
	/** L'intervallo di tempo (in ms) che intercorre tra un immagine del set e la successiva. */
	private int delayTime;
	/**indica lo stato dell'animazione*/
	private transient AnimationState state;
	/** L'oggetto responsabile di iterare le immagini del set. */
	private transient Thread thread;
	/** Il contenitore che contiene questa animazione. */
	private transient Component drawingComponent;
	/** L'indice che cicla tra le immagini del set. */
	private transient int index;
	
	/** Costruisce una DiscreteAnimation, noto l'intervallo di separazione
	 *  temporale tra le varie immagini discrete.
	 * @param _parent     il contenitore di questa animazione.
	 * @param _delayTime  l'intervallo di spaziatura temporale in ms. */
	public DiscreteAnimation(JComponent _parent, int _delayTime){
		this.drawingComponent = _parent;
		this.delayTime = _delayTime;
		this.frames = new Vector<ImageIcon>();
		this.index = 0;
		this.thread=new Thread(this);
		this.state=AnimationState.NEW;
	}
	
	
	private void writeObject(ObjectOutputStream out) throws IOException {
		out.defaultWriteObject(); 
	}

	/**metodo eseguito durante la deserializzazione per reperire un'istanza di DiscreteAnimation.
	 * Il metodo, oltre a reperire l'animazione, provvede a farla partire immediatamente.
	 * Per ulteriori informazioni leggi l'indirizzo <a href="http://java.sun.com/developer/technicalArticles/Programming/serialization/"> sun site </a>
	 * 
	 * @param in lo stream dati che viene usato per la deserializzazione
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
		in.defaultReadObject();
		this.startAnimation();
	}
	
	/** Aggiunge una nuova immagine al set. 
	 * @param image l'immagine da aggiungere. */
	public void addFrame(BufferedImage image){
		this.frames.add(new ImageIcon(image));
	}
	
	/** Specificata un'immagine che dovrebbe essere nel set, la rimuove.
	 *  @param image l'immagine da rimuovere. */
	public void removeFrame(BufferedImage image){
		if (this.frames.contains(image)){
			this.frames.remove(image);
		} else {System.err.println("Immagine inesistente nel set.");}
	}
	
	/** Rimuove tutte le immagini presenti nel set. */
	public void removeAllFrames(){
		this.frames.removeAllElements();
	}
	
	/** Sostituisce un immagine nel set con un'altra alla stessa posizione.
	 * @param toBeInserted   l'immagine da inserire.
	 * @param toBeDiscarded  l'immagine da scartare. */
	public void replaceFrame(BufferedImage toBeInserted, BufferedImage toBeDiscarded){
		int index = this.frames.indexOf(toBeDiscarded);
		this.frames.remove(toBeDiscarded);
		this.frames.add(index, new ImageIcon(toBeInserted));
	}
	
	/** Setter per il delayTime.
	 * @param _delayTime il delayTime da settare.*/
	public void setDelayTime(int _delayTime){
		this.delayTime = _delayTime;
	}
	
	/** Getter per il delayTime.
	 * @return il delayTime. */
	public int getDelayTime(){
		return this.delayTime;
	}
	
	//clona tutto ma non fa partire l'animazione
	@Override
	public DiscreteAnimation clone() throws CloneNotSupportedException{
		DiscreteAnimation cloned=(DiscreteAnimation) super.clone();
		cloned.delayTime=this.delayTime;
		cloned.frames=(Vector<ImageIcon>) this.frames.clone();
		cloned.state=AnimationState.NEW;
		cloned.thread=new Thread(cloned);
		cloned.drawingComponent=this.drawingComponent;
		cloned.index=0;
		return cloned;
	}
	
	/**esegue una serie di istruzioni per far avviare subito l'animazione corrente
	 * 
	 */
	private void startAnimation(){
		if (this.thread==null){
			this.thread=new Thread(this);
			this.state=AnimationState.NEW;
		}
		if (this.state==AnimationState.NEW){
			this.thread.start();
			this.state=AnimationState.RUNNING;
		}
	}

	/**assicura l'avvio sicuro dell'animazione
	 * 
	 */
	public void start(){
		this.startAnimation();
	}
	/**
	 * 
	 * @return TRUE se l'animazione sta girando, FALSE altrimenti
	 */
	public boolean isRunning(){
		return this.state==AnimationState.RUNNING;
	}
	
	/** Riproduce graficamente l'animazione.
	 */
	public BufferedImage paint(Graphics g){
		BufferedImage img=new BufferedImage(this.frames.get(index).getIconWidth(),this.frames.get(index).getIconHeight(),BufferedImage.TYPE_4BYTE_ABGR);
		img.getGraphics().drawImage(this.frames.get(index).getImage(),0,0,null);
		return img;
	}
	/**mette in pausa l'animazione. Tale animazione può essere riattivata usando il comando {@link #restart()}
	 * 
	 */
	public void pause(){
		this.state=AnimationState.PAUSED;
	}
	/**riattiva l'animazione che era stata messa in pausa da {@link #pause()}
	 * 
	 */
	public void restart(){
		if (this.state==AnimationState.PAUSED){
			this.state=AnimationState.RUNNING;
			this.notify();
		}
	}
	/**termina l'animazione che non può più essere riattivata
	 * 
	 */
	public void stop(){
		this.state=AnimationState.TERMINATED;
	}

	@Override
	public void run() {
		synchronized (this){ 
			while (this.state!=AnimationState.TERMINATED){
				//************ aspetto se è in PAUSA ******************
				while (this.state==AnimationState.PAUSED){
					try {
						this.wait();
					} catch (InterruptedException error) {}
				}
				//************ disegno dell'animazione *******************
				try{
					this.drawingComponent.repaint();
				}catch (NullPointerException drawingnull){
					System.out.println("drawing component null!");
				}
				//************ aspetto il delayTime **********************
				try {
					Thread.sleep(this.delayTime);
				} catch (InterruptedException error) {}
				//************ passo alla prossima figura *******************
				this.index++;
				if (this.index > (this.frames.size()-1)) {
					this.index = 0;
				}
			}
		}
		
		
		/*while(this.state==AnimationState.RUNNING){
			if (drawingComponent!=null){
				//in caso parent non sia cablato, non è necessario avvisare il componente padre.
				//Se il parent viene cablato in un qualunque momento il repaint incomincerà ad avere successo ^^
				 
				System.out.println("disegno su "+this.drawingComponent);
				this.drawingComponent.repaint();
			}
			try {
				Thread.sleep(this.delayTime);
			} catch (InterruptedException error) {
				error.printStackTrace();
			}
			this.index++;
			if (this.index > (this.frames.size()-1)) {
				this.index = 0;
			}
		}*/
	}

	/**
	 * @return the parent. For further information visit {@link #drawingComponent}
	 */
	public Component getParent() {
		return drawingComponent;
	}

	/**
	 * @param parent imposta il componente che disegnerà l'animazione
	 */
	public void setParent(Component parent) {
		this.drawingComponent = parent;
	}
	
	
}
